package com.hk.core.test.mockito;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.RandomUtils;
import org.mockito.Mockito;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveListOperations;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.data.redis.core.ReactiveValueOperations;
import reactor.core.publisher.Mono;

import java.time.Duration;

import static org.mockito.ArgumentMatchers.*;

@SuppressWarnings({"unchecked"})
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class ReactiveRedisTemplateMockito {

    /**
     * mockito ReactiveStringRedisTemplate
     */
    public static ReactiveStringRedisTemplate reactiveStringRedisTemplate() {
        ReactiveStringRedisTemplate stringRedisTemplate = Mockito.mock(ReactiveStringRedisTemplate.class);
        mockitoReactiveConnection(stringRedisTemplate);
        redisReactiveTemplateListMockito(stringRedisTemplate);
        redisTemplateValueMockito(stringRedisTemplate);
        return stringRedisTemplate;
    }

    /**
     * mockito ReactiveStringRedisTemplate
     */
    public static <K, V> ReactiveRedisTemplate<K, V> reactiveRedisTemplate() {
        ReactiveRedisTemplate<K, V> stringRedisTemplate = Mockito.mock(ReactiveRedisTemplate.class);
        mockitoReactiveConnection(stringRedisTemplate);
        redisReactiveTemplateListMockito(stringRedisTemplate);
        redisTemplateValueMockito(stringRedisTemplate);
        return stringRedisTemplate;
    }

    public static <K, V> void mockitoReactiveConnection(ReactiveRedisTemplate<K, V> redisTemplate) {
        ReactiveRedisConnectionFactory connectionFactory = Mockito.mock(ReactiveRedisConnectionFactory.class);
        Mockito.when(redisTemplate.getConnectionFactory()).thenReturn(connectionFactory);
        ReactiveRedisConnection redisConnection = Mockito.mock(ReactiveRedisConnection.class);
        Mockito.when(connectionFactory.getReactiveConnection()).thenReturn(redisConnection);
    }

    public static <K, V> void redisTemplateValueMockito(ReactiveRedisTemplate<K, V> redisTemplate) {
        ReactiveValueOperations<K, V> operations = Mockito.spy(ReactiveValueOperations.class);
        Mockito.when(redisTemplate.opsForValue()).thenReturn(operations);

        Mockito.when(operations.set(any(), any())).thenReturn(Mono.just(true));
        Mockito.when(operations.set(any(), any(), any(Duration.class)).thenReturn(Mono.defer(() -> Mono.just(RandomUtils.nextBoolean()))));

        Mockito.when(operations.setIfAbsent(any(), any())).thenReturn(Mono.just(true));
        Mockito.when(operations.setIfAbsent(any(), any(), any(Duration.class))).thenReturn(Mono.just(true));
        Mockito.when(operations.multiSet(anyMap()).thenReturn(Mono.just(true)));
        Mockito.when(operations.multiSetIfAbsent(anyMap())).thenReturn(Mono.just(true));

        Mockito.when(operations.getAndDelete(any())).thenReturn(Mono.empty());
        Mockito.when(operations.getAndExpire(any(), any(Duration.class))).thenReturn(Mono.empty());
        Mockito.when(operations.getAndPersist(any())).thenReturn(Mono.empty());
        Mockito.when(operations.getAndSet(any(), any())).thenReturn(Mono.empty());
        Mockito.when(operations.increment(any())).thenReturn(Mono.just(1L));
        Mockito.when(operations.increment(any(), anyLong())).thenReturn(Mono.just(1L));
        Mockito.when(operations.increment(any(), anyDouble())).thenReturn(Mono.just(1D));
        Mockito.when(operations.decrement(any())).thenReturn(Mono.just(0L));
        Mockito.when(operations.decrement(any(), anyLong())).thenReturn(Mono.just(0L));
        Mockito.when(operations.append(any(), any())).thenReturn(Mono.just(1L));
        Mockito.when(operations.set(any(), any(), anyLong())).thenReturn(Mono.just(1L));
        Mockito.when(operations.setBit(any(), anyLong(), anyBoolean())).thenReturn(Mono.just(true));
        Mockito.when(operations.getAndDelete(any())).thenReturn(Mono.empty());
    }

    public static <K, V> void redisReactiveTemplateListMockito(ReactiveRedisTemplate<K, V> redisTemplate) {
        ReactiveListOperations<K, V> operations = Mockito.spy(ReactiveListOperations.class);
        Mockito.when(redisTemplate.opsForList()).thenReturn(operations);
        Mockito.when(operations.leftPush(any(), (V) any(Object[].class))).thenReturn(Mono.empty());
        Mockito.when(operations.leftPushAll(any(), (V) any(Object[].class))).thenReturn(Mono.empty());
        Mockito.when(operations.leftPushAll(any(), anyCollection())).thenReturn(Mono.empty());
        Mockito.when(operations.leftPushIfPresent(any(), any())).thenReturn(Mono.empty());
        Mockito.when(operations.leftPush(any(), any(), any())).thenReturn(Mono.empty());

        Mockito.when(operations.rightPush(any(), any())).thenReturn(Mono.empty());
        Mockito.when(operations.rightPushAll(any(), (V) any(Object[].class))).thenReturn(Mono.empty());
        Mockito.when(operations.rightPushAll(any(), anyCollection())).thenReturn(Mono.empty());
        Mockito.when(operations.rightPushIfPresent(any(), any())).thenReturn(Mono.empty());
        Mockito.when(operations.rightPush(any(), any(), any())).thenReturn(Mono.empty());

        Mockito.when(operations.move(any(), any())).thenReturn(Mono.empty());
        Mockito.when(operations.move(any(), any(), any())).thenReturn(Mono.empty());
        Mockito.when(operations.move(any(), any(), any(), any())).thenReturn(Mono.empty());
        Mockito.when(operations.move(any(), any(), any(), any(), any())).thenReturn(Mono.empty());

        Mockito.when(operations.set(any(), anyLong(), any())).thenReturn(Mono.just(true));
        Mockito.when(operations.remove(any(), anyLong(), any())).thenReturn(Mono.empty());

        Mockito.when(operations.leftPop(any())).thenReturn(Mono.empty());
        Mockito.when(operations.leftPop(any(), any(Duration.class))).thenReturn(Mono.empty());

        Mockito.when(operations.rightPop(any())).thenReturn(Mono.empty());
        Mockito.when(operations.rightPop(any(), any(Duration.class))).thenReturn(Mono.empty());

        Mockito.when(operations.rightPopAndLeftPush(any(), any())).thenReturn(Mono.empty());
        Mockito.when(operations.rightPopAndLeftPush(any(), any(), any(Duration.class))).thenReturn(Mono.empty());
    }
}
